#ifndef __INCLUDE_GUARD_D2CAE848_1C1E_4362_8DFE_163B2EA2A97D
#define __INCLUDE_GUARD_D2CAE848_1C1E_4362_8DFE_163B2EA2A97D
#ifdef _MSC_VER
	#pragma once
#endif

#include "../rt/basic_definitions.h"

struct PointLightSource
{
	Point position;
	float4 intensity, falloff;
	//falloff formula: (.x  / dist^2 + .y / dist + .z) * intensity;
};


struct DepthStateKey
{
	typedef int t_data;
};

class IntegratorImpl : public Integrator
{
public:
	enum {_MAX_BOUNCES = 7};

	GeometryGroup *scene;
	std::vector<PointLightSource> lightSources;
	float4 ambientLight;
	Ray incomRay;

	IntegratorImpl()
	{
		state.value<DepthStateKey>() = 0;
	}

	virtual float4 getRadiance(const Ray &_ray)
	{
		state.value<DepthStateKey>()++;

		float4 col = float4::rep(0);

		incomRay = _ray;

		if(state.value<DepthStateKey>() < _MAX_BOUNCES)
		{
			Primitive::IntRet ret = scene->intersect(_ray, FLT_MAX);
			if(ret.distance < FLT_MAX && ret.distance >= Primitive::INTEPS())
			{
				SmartPtr<Shader> shader = scene->getShader(ret);
				if(shader.data() != NULL)
				{
					col += shader->getAmbientCoefficient() * ambientLight;

					Point intPt = _ray.o + ret.distance * _ray.d;

					for(std::vector<PointLightSource>::const_iterator it = lightSources.begin(); it != lightSources.end(); it++){
						float vis = visibleLS(intPt, it->position);
						if(vis!=0.f)
						{
							Vector lightD = it->position - intPt;
							float4 refl = shader->getReflectance(-_ray.d, lightD);
							float dist = lightD.len();
							float fallOff = it->falloff.x / (dist * dist) + it->falloff.y / dist + it->falloff.z;
							col += float4::rep(vis) * refl * float4::rep(fallOff) * it->intensity;
						}

						col += shader->getIndirectRadiance(-_ray.d, this);
					}
				}
			}
		}

		state.value<DepthStateKey>()--;

		return col;
	}
private:

	float visibleLS(const Point& _pt, const Point& _pls)
	{
		//Ray r from hitpoint to lightsource
        Ray r;
        r.d = _pls - _pt;
		//origin is a bit shifted to the lightsource, to avoid intersection with the primitive of the hitpoint
        r.o = _pt + Primitive::INTEPS() * r.d;
		r.time = r.time;
		float maxdist = 1.1f;
		Primitive::IntRet ret = scene->intersect(r, maxdist);
		
		std::vector<float> densities;

		//if the object hitten by the ray lays behind the lightsource
		if(ret.distance >= 1 - Primitive::INTEPS())
			return 1.f;
		
		SmartPtr<Shader> shader = scene->getShader(ret);
		//if the hitten object isn't transparent, 
		if(shader->getDensity()==-1.f) return 0.f;
		
		//if the hitpoint is not behind the lightsource
		while(ret.distance<maxdist){
			float dens = shader->getDensity();
			//if the actual hitten object is transparent
			if(dens!=-1.f){
				densities.push_back(0.1f*dens);
				//update the ray
				Point hit = r.getPoint(ret.distance);
				r.o = hit + Primitive::INTEPS()*r.d;
				//update maxdist and shoot the ray again
				maxdist -= ret.distance;
				ret = scene->intersect(r, maxdist);
			}
			//if there is a non-transparent object
			else 
				return 0.f;
		}
		//if the while loop ended without finding a non-transparent object
		float sum = 1.f;
		for(uint i=0;i<densities.size();i++)
			sum -= densities[i];

		return sum<0.f ? -1.f : sum;///(float)densities.size();
		
		//original version
		//return ret.distance >= 1 - Primitive::INTEPS();
	}
};


#endif //__INCLUDE_GUARD_D2CAE848_1C1E_4362_8DFE_163B2EA2A97D
